/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.nhncorp.neptune.master;

import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.nhncorp.neptune.changelog.ChangeLogServerIF;
import com.nhncorp.neptune.client.CellFilter;
import com.nhncorp.neptune.client.NTable;
import com.nhncorp.neptune.client.Row;
import com.nhncorp.neptune.client.RowFilter;
import com.nhncorp.neptune.common.Constants;
import com.nhncorp.neptune.common.LockServiceFactory;
import com.nhncorp.neptune.common.conf.NConfiguration;
import com.nhncorp.neptune.common.ipc.NRPC;
import com.nhncorp.neptune.common.util.NetworkUtil;
import com.nhncorp.neptune.tablet.TableSchema;
import com.nhncorp.neptune.tablet.TabletInfo;
import com.nhncorp.neptune.tablet.TabletReport;
import com.nhncorp.neptune.tabletserver.DataServiceProtocol;
import com.nhncorp.neptune.tabletserver.TabletManagerProtocol;
import com.nhncorp.neptune.tabletserver.TabletServerInfo;
import com.nhncorp.neptune.tabletserver.TabletServerStatus;
import com.nhncorp.pleiades.client.LockService;
import com.nhncorp.pleiades.protocol.LockMetaData;

public class JspHelper {
  NConfiguration conf;
  LockService lockService;
  DecimalFormat df;
  public JspHelper() throws IOException {
    conf = new NConfiguration();
    lockService = LockServiceFactory.getLockService(conf, "web_admin", false);
    df = new DecimalFormat("###,###,###,###");
  }
  
  public String getMasterHostName() throws IOException {
    return NeptuneMaster.getMasterServerHostName(lockService);
  }
  
  public void getClusterInfo(Writer out) throws IOException {
    out.write("<table border=\"0\">");
    out.write("<tr><td width=\"100\"><b>Started:</b></td><td>" + NeptuneMaster.neptuneMaster.masterStartTime + "</td></tr>");
    out.write("<tr><td width=\"100\"><b>Master Lock:</b></td><td>" + NeptuneMaster.neptuneMaster.masterInitTime + "</td></tr>");
    out.write("<tr><td width=\"100\"><b>Version:</b></td><td>" + Constants.MAJOR_VERSION + "." + Constants.MINOR_VERSION + "</td></tr>");
    
    long free = Runtime.getRuntime().freeMemory();
    long max = Runtime.getRuntime().maxMemory();
    long total = Runtime.getRuntime().totalMemory();
    
    String memory = df.format(free/1024) + "/" + df.format(total/1024) + " KB, max=" + df.format(max/1024) + " KB";
    
    out.write("<tr><td width=\"100\"><b>Heap:</b></td><td>" + memory + "</td></tr>");
    out.write("</table>");
  }

  public void getChangeLogServers(Writer out) throws IOException {
    String[] addrList = null;
    try {
      addrList = getChangeLogServerAddressList();
    } catch (IOException e) {
      out.write("<h3> Fail to get change log server list </h3>");
      out.write("<h1> " + e.toString() + "</h1>");
      return;
    }
    Arrays.sort(addrList);
    
    out.write("<h3>Live ChangeLogServers: " + addrList.length + "</h3>");
    out.write("<table border=\"1\" width=\"100%\">");
    out.write("<tr><th>Node</th><th>Status</th></tr>");

    for(String addr : addrList) {
      String status = "ok";
        
      try {
        ChangeLogServerIF changeLogServer = (ChangeLogServerIF) NRPC.getProxy(ChangeLogServerIF.class, 
            ChangeLogServerIF.versionID, NetworkUtil.getAddress(addr), conf);  
        changeLogServer.test();
      } catch(IOException e) {
        status = e.toString();
      }
      
      out.write("<tr>");
//    out.write("<td><a href=\"changelogserver.jsp?host=" + addr + "\">" + addr + "</a></td>");
      out.write("<td>" + addr + "</td>");
      out.write("<td>" + status + "</td>");
      out.write("</tr>");
    }
    
    out.write("</table>");
  }
  
  private String[] getChangeLogServerAddressList() throws IOException {
    Map<String, LockMetaData> metaMap = lockService.readDir(Constants.CHANGELOG_SERVER);
    String[] addrList = new String[metaMap.size()];
    int i = 0;
    for(String fileName : metaMap.keySet()) {
      addrList[i] = NetworkUtil.fileNameToHostName(fileName);
	  addrList[i] = addrList[i].substring(addrList[i].lastIndexOf("/") + 1);
	  i++;
    }

    return addrList;
  }	

  public void getTabletServers(Writer out) throws IOException {
    TabletServerInfo[] tabletServers = NeptuneMaster.neptuneMaster.getTabletServerInfos();
    if(tabletServers != null) {
      Arrays.sort(tabletServers);
    }
    
    if(tabletServers != null) {
      out.write("<h3>Live TabletServers: " + tabletServers.length + "</h3>");
      out.write("<table border=\"1\" width=\"100%\">");
      out.write("<tr><th>Node</th><th>Status</th></tr>");
      for(TabletServerInfo eachServer: tabletServers) {
        String hostName = eachServer.getHostName();
        String status = "ok";
        try {
          DataServiceProtocol tabletServer = (DataServiceProtocol) NRPC.getProxy(DataServiceProtocol.class, 
              DataServiceProtocol.versionID, 
              NetworkUtil.getAddress(hostName), conf);  
          tabletServer.test(0, "");
        } catch (Exception e) {
          status = "timeout";
        }
        out.write("<tr>");
        out.write("<td><a href=\"tabletserver.jsp?tabletserver=" + hostName + "\">" + hostName + "</a></td>");
        out.write("<td>" + status + "</td>");
        out.write("</tr>");
      }    
      out.write("</table>");
    } else {
      out.write("<h3>Live TabletServers: 0</h3>");
    }
  }
  
  public void getTables(Writer out) throws IOException {
    TableSchema[] tableSchemas = NeptuneMaster.neptuneMaster.listTables();
    if(tableSchemas != null) {
      Arrays.sort(tableSchemas);
    }
    
    if(tableSchemas != null) {
      out.write("<h3>Table: " + tableSchemas.length + "</h3>");
      out.write("<table border=\"1\" width=\"500\">");
      out.write("<tr><th>Table Name</th><th>Owner</th><th># version</th></tr>");
      for(TableSchema eachTable: tableSchemas) {
        String tableName = eachTable.getTableName();
        out.write("<tr>");
        out.write("<td><a href=\"table.jsp?table=" + tableName + "\">" + tableName + "</a></td>");
        out.write("<td>" + eachTable.getOwner() + "</td>");
        out.write("<td>" + eachTable.getNumOfVersion() + "</td>");
        out.write("</tr>");
      }    
      out.write("</table>");
    } else {
      out.write("<h3>Table: 0</h3>");
    }
  }
  
  
  public void getTabletDetailInfo(Writer out, String tabletName, String tabletServerHost) throws IOException {
    if(tabletServerHost == null) {
      out.write("Wrong tablet server");
      return;
    }

    TabletManagerProtocol tabletServer = (TabletManagerProtocol) NRPC.getProxy(TabletManagerProtocol.class, 
        TabletManagerProtocol.versionID, 
        NetworkUtil.getAddress(tabletServerHost), conf);
    
    TabletServerStatus tabletServerStatus = tabletServer.getServerStatus();

    out.write("<h1>" + tabletName + "(" + tabletServerHost + ")</h1>");
    out.write("<b>Tablet Server Summary</b>");
    out.write("<table border=\"0\">");
    out.write("<tr><td width=\"100\"><b>Started:</b></td><td>" + (new Date(tabletServerStatus.getServerStartTime())) + "</td></tr>");
    
    long free = tabletServerStatus.getFreeMemory();
    long max = tabletServerStatus.getMaxMemory();
    long total = tabletServerStatus.getTotalMemory();
    
    String threadLoad = "MajorCompaction Task: " + tabletServerStatus.getNumMajorThread() + ", " +
    "Split Task: " + tabletServerStatus.getNumSplitThread() + ", " +
    "Etc Task: " + tabletServerStatus.getNumEtcThread();
    
    out.write("<hr/>");
    out.write("<b>Tablet</b>");
    String memory = "Free: " + df.format(free/1024) + " KB, Total: " + df.format(total/1024) + " KB, Max: " + df.format(max/1024) + " KB";
    out.write("<tr><td width=\"100\"><b>Heap:</b></td><td>" + memory + "</td></tr>");
    out.write("<tr><td width=\"100\"><b>Load:</b></td><td>" + threadLoad + "</td></tr>");
    
    out.write("</table>");
  
    TabletReport tabletReport = tabletServer.getTabletDetailInfo(tabletName);
    
    tabletReport.print(out);
  }
  
  public void getTabletServerDetailInfos(Writer out, String tabletServerHost) throws IOException {
    if(tabletServerHost == null) {
      out.write("Wrong tablet server");
      return;
    }
    
    TabletManagerProtocol tabletServer = (TabletManagerProtocol) NRPC.getProxy(TabletManagerProtocol.class, 
        TabletManagerProtocol.versionID, 
        NetworkUtil.getAddress(tabletServerHost), conf);
    
    TabletServerStatus tabletServerStatus = tabletServer.getServerStatus();
    
    out.write("<h1>" + tabletServerHost + "</h1>");
    out.write("<table border=\"0\">");
    out.write("<tr><td width=\"100\"><b>Started:</b></td><td>" + (new Date(tabletServerStatus.getServerStartTime())) + "</td></tr>");
    
    long free = tabletServerStatus.getFreeMemory();
    long max = tabletServerStatus.getMaxMemory();
    long total = tabletServerStatus.getTotalMemory();
    
    String memory = "Free: " + df.format(free/1024) + " KB, Total: " + df.format(total/1024) + " KB, Max: " + df.format(max/1024) + " KB";
    out.write("<tr><td width=\"100\"><b>Heap:</b></td><td>" + memory + "</td></tr>");
    
    String threadLoad = "MajorCompaction Task: " + tabletServerStatus.getNumMajorThread() + ", " +
                        "Split Task: " + tabletServerStatus.getNumSplitThread() + ", " +
                        "Etc Task: " + tabletServerStatus.getNumEtcThread();
    out.write("<tr><td width=\"100\"><b>Load:</b></td><td>" + threadLoad + "</td></tr>");
    out.write("</table>");
    out.write("<hr/>");
    
    TabletInfo[] tabletInfos = tabletServer.reportTablets();
    if(tabletInfos != null) {
      Arrays.sort(tabletInfos);
    }

    if(tabletInfos != null) {
      out.write("<h3>Tablet: " + tabletInfos.length + "</h3>");
      printTabletReport(out, tabletInfos, 1, tabletInfos.length);
      
    } else {
      out.write("<h3>Tablet: 0</h3>");
    }
    out.write("<hr/>");
    out.write("<h3>Tx Statistics</h3>");
    out.write("<table border=\"1\" width=\"800\">");
    for(String eachItem: tabletServerStatus.getTxStatistics()) {
      out.write("<tr><td>" + eachItem + "</td></tr>");
    }
    out.write("</table>");    
  }
  
  private String getPageMove(String url, String paramName, String paramValue, int recordTotalCount, int page, int maxOfPage) throws IOException {
    int startNum = (page - 1) * maxOfPage;
    int endNum   = page * maxOfPage;
    
    boolean next = true;
    boolean previous = true;
    if(endNum >= recordTotalCount) {
      endNum = recordTotalCount;
      next = false;
    }
    
    if(page == 1) {
      previous = false;
    }

    String result = "<table border=\"0\" width=\"100%\"><tr><td width=\"50%\">";
    if(previous) {
      result += ("<a href=\"" + url + "?" + paramName + "=" + paramValue + "&page=" + (page - 1) + "\">[Previous]</a>&nbsp;&nbsp;");
    }
    if(next) {
      result += ("<a href=\"" + url + "?" + paramName + "=" + paramValue + "&page=" + (page + 1) + "\">[Next]</a>");
    }
    
    result += "</td><td width=\"50%\" align=\"right\"><a href=\"/status.jsp\">[Goto Main]</a></td></tr></table>";
    return result;
  }
  
  private void printTabletReport(Writer out, TabletInfo[] tabletInfos, int page, int maxOfPage) throws IOException {
    int errorCount = 0;
    long totalMapFileSize = 0;
    long totalIndexFileSize = 0;
    long totalMemorySize = 0;
    
    int startNum = (page - 1) * maxOfPage;
    int endNum   = page * maxOfPage;
    if(endNum >= tabletInfos.length) {
      endNum = tabletInfos.length;
    }
    
    int count = startNum + 1;
    
    out.write("<table border=\"1\" width=\"100%\">");
    out.write("<tr><th width=\"5%\">No</th><th width=\"5%\">Table Name</th><th width=\"10%\">Tablet Name</th><th width=\"10%\">host</th><th width=\"20%\">end row</th><th width=\"50%\">Tablet summary</th></tr>");
    
    StringBuilder sb = new StringBuilder(1000);
    for(int i = startNum ;i < endNum; i++) {
      sb.setLength(0);
      TabletInfo eachTabletInfo = tabletInfos[i];
      try {
        if(eachTabletInfo.getAssignedHostName() == null) {
          sb.append("<tr>");
          sb.append("<td colspan=\"6\">Assigned host name is null" + eachTabletInfo.getTabletName() + "</td>");
          sb.append("</tr>");
          out.write(sb.toString());
          errorCount++;
          continue;
        }
        TabletManagerProtocol tabletServer = (TabletManagerProtocol) NRPC.getProxy(TabletManagerProtocol.class, 
            TabletManagerProtocol.versionID, 
            NetworkUtil.getAddress(eachTabletInfo.getAssignedHostName()), conf);
        
        TabletReport tabletReport = tabletServer.getTabletDetailInfo(eachTabletInfo.getTabletName());
        if(tabletReport == null) {
          sb.append("<tr>");
          sb.append("<td colspan=\"6\">Error reporting TabletInfo:" + eachTabletInfo.getTabletName() + "</td>");
          sb.append("</tr>");
          errorCount++;
          out.write(sb.toString());
          continue;
        }
        String tableName = eachTabletInfo.getTableName();
        String tabletName = eachTabletInfo.getTabletName();
        Row.Key endRowKey = eachTabletInfo.getEndRowKey();
        String endRowKeyStr = new String(endRowKey.getBytes(), "EUC-KR");
        if(endRowKey.equals(Row.Key.MAX_KEY)) {
          endRowKeyStr = "MAX";
        }
        if(endRowKeyStr.length() > 100) {
          endRowKeyStr = endRowKeyStr.substring(0, 100) + "...";
        }
        
        sb.append("<tr>");
        sb.append("<td>" + count + "</td>");
        sb.append("<td>" + tableName + "</td>");
        sb.append("<td><a href=\"tablet.jsp?tablet=" + tabletName + "&tabletserver=" + eachTabletInfo.getAssignedHostName() + "\">" + tabletName + "</a></td>");
        sb.append("<td>" + eachTabletInfo.getAssignedHostName() + "</td>");
        sb.append("<td>" + endRowKeyStr + "</td>");
        sb.append("<td><nobr>");
        
        out.write(sb.toString());
        tabletReport.print(out);
        out.write("</nobr></td>");
        out.write("</tr>");
        totalMapFileSize += tabletReport.sumMapFileSize();
        totalIndexFileSize += tabletReport.sumMapFileIndexSize();
        totalMemorySize += tabletReport.getMemoryTabletSize();
        count++;
      } catch (Exception e) {
        sb.append("<td colspan=\"6\">Error reporting TabletInfo:" + eachTabletInfo.getTabletName() + "</td>");
        out.write(sb.toString());
      }
    }
    out.write("</table>");
    out.write("# of Tablets:" + tabletInfos.length + ", Total MapFile size: " + df.format(totalMapFileSize/1024) + " KB, " +
        "Index File size: " + df.format(totalIndexFileSize/1024) + " KB, " +
    		"MemoryTablet: " + df.format(totalMemorySize/1024) + " KB, Report error: " + errorCount);
  }
  public void getTableInfo(Writer out, String tableName, int page) throws IOException {
    if(tableName == null) {
      out.write("<h3>Invalid table name</h3>");
      return;
    }
    out.write("<h3>Table: " + tableName + "</h3>");
    NTable ntable = NTable.openTable(conf, tableName);
    TabletInfo[] listTabletInfos = ntable.listTabletInfos();
    if(listTabletInfos == null) {
      out.write("No Tablets");
      return;
    }
    out.write(getPageMove("/table.jsp", "table", tableName, listTabletInfos.length, page, 1000));
    printTabletReport(out, listTabletInfos, page, 1000);
  }

  public Row[] runQuery(String tableName, String[] columns, String startRowKey, 
      String endRowKey, String operation, String paramAllVersion) throws Exception {
    if(tableName == null || tableName.length() == 0 || columns == null || columns.length == 0 ||
        startRowKey == null || startRowKey.trim().length() == 0) {
      return null;
    }
    NTable ntable = NTable.openTable(conf, tableName);
    if(endRowKey == null || endRowKey.trim().length() == 0) {
      endRowKey = startRowKey;
    }
    
    RowFilter rowFilter = new RowFilter(new Row.Key(startRowKey), 
        new Row.Key(endRowKey),
        Integer.parseInt(operation));

    boolean allVersion = "Y".equals(paramAllVersion);
    
    for(String eachColumn: columns) {
      CellFilter cellFilter = new CellFilter(eachColumn);
      if(allVersion) {
        cellFilter.setStartTimestamp(Long.MIN_VALUE);
        cellFilter.setEndTimestamp(Long.MAX_VALUE);
      }
      rowFilter.addCellFilter(cellFilter);
    }
    
    return ntable.gets(rowFilter);
  }
  
  public void showQueryCondition(Writer out, String selectedTableName, String[] selectedColumns, String startRowKey, 
      String endRowKey, String paramOperation, String paramAllVersion) throws Exception {
    TableSchema[] tableSchemas = NeptuneMaster.neptuneMaster.listTables();
    if(tableSchemas == null) {
      return;
    }
    
    if(tableSchemas != null) {
      Arrays.sort(tableSchemas);
    }
    
    out.write("<table border=\"1\" width=\"100%\">");
    out.write("<tr><td width=33% valign=top>");
    
    //show tables
    TableSchema selectedTableSchema = null;
    if(tableSchemas != null) {
      out.write("<table border=\"0\" width=\"100%\">\n");
      out.write("<tr><th align=left>Table Name</th></tr>\n");
      for(TableSchema eachTable: tableSchemas) {
        String eachTableName = eachTable.getTableName();
        String bgColor = "#ffffff";
        if(selectedTableName != null && eachTableName.equals(selectedTableName)) {
          bgColor = "#c5beb9";
          selectedTableSchema = eachTable;
        }
        out.write("<tr><td bgColor=\"" + bgColor + "\"><a href=\"query.jsp?tableName=" + eachTableName + "\">" + eachTableName + "</a></td></tr>\n");
      }    
      out.write("</table>\n");
    } 
    out.write("</td><td width=33% valign=top>\n");
    
    Set<String> columnNameSet = new HashSet<String>();
    if(selectedColumns != null) {
      for(String eachColumn: selectedColumns) {
        columnNameSet.add(eachColumn);
      }
    }
    //show columns
    out.write("<table border=\"0\" width=\"100%\">\n");
    out.write("<tr><th align=left>Column Name</th></tr>\n");
    if(selectedTableSchema != null) {
      String[] schemaColumnNames = selectedTableSchema.getColumnsArray();
      
      for(String eachColumn: schemaColumnNames) {
        String checked = "";
        if(columnNameSet.contains(eachColumn)) {
          checked = "checked";
        }
        out.write("<tr><td><input type=\"checkbox\" name=\"columnName\" " + checked + " value=\"" + eachColumn + "\">" + eachColumn + "</td></tr>\n");
      }    
    } 
    out.write("</table>\n");
    out.write("</td><td width=33% valign=top>\n");
    
    //rowkey
    if(startRowKey == null) {
      startRowKey = "";
    }
    if(endRowKey == null) {
      endRowKey = "";
    }
    
    String[] opNames = new String[]{"=","like","between"};
    int operation = 0;
    if(paramOperation != null) {
      operation = Integer.parseInt(paramOperation);
    }
    
    String allVersion = "";
    if("Y".equals(paramAllVersion)) {
      allVersion = "checked";
    }
    
    out.write("Start RowKey:<input type=\"text\" size=\"30\" name=\"startRowKey\" value=\"" + startRowKey + "\"><p>\n");
    out.write("End RowKey:<input type=\"text\" size=\"30\" name=\"endRowKey\" value=\"" + endRowKey + "\"><p>\n");
    out.write("Operation:");
    for(int i = 0; i < opNames.length; i++) {
      String checked = (i == operation ? "checked" : "");
      out.write("<input type=\"radio\" name=\"operation\" " + checked + " value=\"" + i + "\"/> " + opNames[i] + "&nbsp;&nbsp;\n");
    }
    out.write("<p><input type=\"checkbox\" name=\"allVersion\" " + allVersion + " value=\"Y\"> all version<p>\n");
    out.write("<input type=\"submit\" value=\"query\" style=\"width:90px\"><p>"); 
    out.write("</td></tr></table>\n");
  }
  
  public List<MethodInfo> getRpcMethods(String type) throws IOException {
    List<MethodInfo> result = new ArrayList<MethodInfo>();
    if("master".equals(type)) {
      addMethod(result, TableManagerProtocol.class.getMethods());
      addMethod(result, TabletMasterProtocol.class.getMethods());
    } else if("tabletserver".equals(type)) {
      addMethod(result, TabletManagerProtocol.class.getMethods());
      addMethod(result, DataServiceProtocol.class.getMethods());
    } else if("changelog".equals(type)) {
      addMethod(result, ChangeLogServerIF.class.getMethods());
    }    
    return result;
  }
  
  private void addMethod(List<MethodInfo>result, Method[] methods) {
    for(Method eachMethod: methods) {
      boolean ok = true;
      String signature = eachMethod.getReturnType().getSimpleName() + " " +
                    eachMethod.getName() + "(";
      
      Class[] params = eachMethod.getParameterTypes();
      
      if(params != null && params.length > 0) {
        for(Class eachParam: params) {
          String paramName = eachParam.getSimpleName();
          if(!"String".equals(paramName) &&
              !"int".equals(paramName) &&
              !"long".equals(paramName) &&
              !"boolean".equals(paramName) &&
              !"Key".equals(paramName) ) {
            ok = false;
            break;
          }
          signature += eachParam.getSimpleName() + ", ";
        }
        signature = signature.substring(0, signature.length() - 2);
      } 
       
      signature += ")";
      if(ok) {
        result.add(new MethodInfo(signature, eachMethod));
      }
    }
  }
  
  public static class MethodInfo {
    String signature;
    Method method;
    
    public MethodInfo(String signature, Method method) {
      this.signature = signature;
      this.method = method;
    }
    
    public String toString() {
      return signature;
    }

    public String getSignature() {
      return signature;
    }

    public Method getMethod() {
      return method;
    }
    
    public String getParamInputForm() {
      String result = ""; 
      Class[] params = method.getParameterTypes();
     
      if(params != null && params.length > 0) {
        for(Class eachParam: params) {
          String paramName = eachParam.getSimpleName();
          result += paramName + ": <input type=\"text\" size=\"10\"><br>";
        }
      }
      
      return result;
    }
  }
  
  public static void main(String[] args) throws IOException {
    JspHelper jspHelper = new JspHelper();
    List<MethodInfo> datas = jspHelper.getRpcMethods("tabletserver");
    
    for(MethodInfo eachData: datas) {
      System.out.println(">>>" + eachData);
    }
  }
}
